#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import os
import sys

import crypto

from PyQt4 import QtCore, QtGui, uic
from safe import KeySafe


APP_PATH = os.path.dirname(os.path.abspath(__file__))
TRAY_ICON = os.path.join(APP_PATH, 'keysafe_tray.png')


class MainWindow(QtGui.QMainWindow):

    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        pw, ok = QtGui.QInputDialog.getText(None,
            self.tr(u'Security'), self.tr(u'Enter Master-Password:'),
            QtGui.QLineEdit.Password)
        self._one_shot = os.urandom(50)
        self._mpw = crypto.encrypt(unicode(pw), self._one_shot)
        if not ok:
            sys.exit()
        self._safe_path = ''
        self._safe = None
        self.categories = None
        self.selected_item = None
        self.tray = None
        self.config = QtCore.QSettings(QtCore.QSettings.IniFormat,
            QtCore.QSettings.UserScope, 'Keysafe', 'Keysafe')
        uic.loadUi('keysafe.ui', self)
        self.btn_show_hide.clicked[bool].connect(self.on_btn_show_hide)
        self.btn_add.clicked.connect(self.on_btn_add)
        self.action_exit.triggered.connect(self.close)
        self.action_save.triggered.connect(self.on_action_save)
        self.action_delete.triggered.connect(self.on_action_delete)
        self.tree.itemClicked.connect(self.on_item_clicked)
        self.tree.itemDoubleClicked.connect(self.on_item_doubleclicked)
        self.init()
        self.setup_tray()

    def tr(self, msg):
        return QtGui.QApplication.translate('MainWindow', msg,
            encoding=QtGui.QApplication.UnicodeUTF8)

    def init(self, with_settings=True):
        if with_settings:
            if not os.path.isfile(unicode(self.config.fileName())):
                safe = QtGui.QFileDialog.getSaveFileName(self,
                    self.tr(u'Select Keysafe file'), os.path.expanduser('~'),
                    self.tr(u'Keysafe Files (*.keysafe)'))
                self._safe_path = unicode(safe)
                if not self._safe_path.endswith('.keysafe'):
                    self._safe_path = u'{0}.keysafe'.format(self._safe_path)
                self.write_settings()
            self.load_settings()
            try:
                self._safe = KeySafe(
                    crypto.decrypt(self._mpw, self._one_shot),
                    self._safe_path
                )
            except:
                QtGui.QMessageBox.critical(self,
                    self.tr(u'Wrong Password'),
                    self.tr(u'You entered the wrong '
                        'Master-Password! Application will exit now.'))
                sys.exit()
            self.status(
                self.tr(u'Keysafe opened from {0}'.format(
                    self._safe_path)))
        self.combo_category.clear()
        self.tree.clear()
        self.categories = []
        for cat, services in self._safe:
            self.categories.append(cat)
            self.combo_category.addItem(cat)
            item = QtGui.QTreeWidgetItem([cat])
            self.tree.addTopLevelItem(item)
            for service in services:
                ser = QtGui.QTreeWidgetItem([service])
                item.addChild(ser)

    def setup_tray(self):
        icon = QtGui.QIcon(TRAY_ICON)
        self.tray = QtGui.QSystemTrayIcon(icon, self)
        self.tray.activated.connect(self.on_tray_clicked)

    def show_tray(self):
        self.tray.show()
        self.tray.showMessage(self.tr(u'Keysafe'),
            self.tr(u'Click to show window.\nDouble-Click to exit.'))

    def on_tray_clicked(self, reason):
        self.show()
        self.tray.hide()

    def status(self, msg, timeout=3000):
        self.statusbar.showMessage(msg, timeout)

    def write_settings(self):
        self.config.beginGroup(u'window')
        self.config.setValue('size', self.size())
        self.config.setValue('pos', self.pos())
        self.config.endGroup()
        self.config.setValue('keysafe/file', self._safe_path)
        self.config.sync()
        self.status(self.tr(u'Settings saved'))

    def load_settings(self):
        self._safe_path = unicode(self.config.value('keysafe/file').toString())
        self.resize(self.config.value('window/size').toSize())
        self.move(self.config.value('window/pos').toPoint())
        self.status(self.tr(u'Settings loaded'))

    def on_action_save(self):
        self._safe.sync(crypto.decrypt(self._mpw, self._one_shot))
        self.status(self.tr(u'Keysafe saved to {0}'.format(self._safe_path)))

    def on_action_delete(self):
        item = self.tree.currentItem()
        if unicode(item.text(0)) in self.categories:
            ok = QtGui.QMessageBox.question(self, self.tr(u'Question'),
                self.tr(u'Are you sure to delete a whole category?'),
                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                QtGui.QMessageBox.Yes)
            if ok == QtGui.QMessageBox.Yes:
                cat = unicode(item.text(0))
                if cat == '_default_':
                    self.status(self.tr(u'_default_ cannot be deleted.'))
                else:
                    self._safe.delete_category(cat)
                    self.status(self.tr(u'Category {0} deleted.'.format(cat)))
            else:
                self.status(self.tr(u'Cancelled'))
        else:
            ok = QtGui.QMessageBox.question(self, self.tr(u'Question'),
                self.tr(u'Are you sure to delete the service?'),
                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                QtGui.QMessageBox.Yes)
            if ok == QtGui.QMessageBox.Yes:
                service = unicode(item.text(0))
                cat = unicode(item.parent().text(0))
                self._safe.delete(service, cat)
                self.status(self.tr(u'{0} deleted from {1}'.format(service,
                                                                   cat)))
            else:
                self.status(self.tr(u'Cancelled'))

    def on_item_clicked(self, item, col):
        if unicode(item.text(0)) in self.categories:
            self.selected_item = None
            self.uname.clear()
            self.pw.clear()
            return
        self.selected_item = item
        self.uname.setText(u'***')
        self.pw.setText(u'***')

    def on_item_doubleclicked(self, item, col):
        if unicode(item.text(0)) in self.categories:
            QtGui.QMessageBox.information(self,
                self.tr(u'Information'),
                self.tr(u'Edit category names is not supported yet.'))
            return
        cat = unicode(item.parent().text(0))
        ser = unicode(item.text(0))
        uname, pw = self._safe.get(
            crypto.decrypt(self._mpw, self._one_shot),
            ser,
            cat
        )
        new_ser, ok = QtGui.QInputDialog.getText(self,
            self.tr(u'Service'), self.tr(u'Enter new service or press <ok>.'),
            text=ser)
        if not ok:
            self.status(self.tr(u'Cancelled'))
            return
        new_uname, ok = QtGui.QInputDialog.getText(self,
            self.tr(u'Username'), self.tr(u'Enter new username or press <ok>.'),
            text=uname)
        if not ok:
            self.status(self.tr(u'Cancelled'))
            return
        new_pw, ok = QtGui.QInputDialog.getText(self,
            self.tr(u'Password'), self.tr(u'Enter new password or press <ok>.'),
            mode=QtGui.QLineEdit.Password, text=pw)
        if not ok:
            self.status(self.tr(u'Cancelled'))
            return
        self._safe.add(
            crypto.decrypt(self._mpw, self._one_shot),
            unicode(new_ser),
            unicode(new_uname),
            unicode(new_pw),
            cat
        )
        if unicode(new_ser) != ser:
            self.status(self.tr(u'Name of service changed. Renaming.'))
            self._safe.delete(ser, cat)
        self.init(False)

    def on_btn_show_hide(self, checked):
        if checked and self.selected_item is not None:
            self.btn_show_hide.setText(self.tr(u'Hide'))
            self.status(self.tr(u'!!! PASSWORD VISIBLE !!!'), 0)
            service = unicode(self.selected_item.text(0))
            category = unicode(self.selected_item.parent().text(0))
            uname, pw = self._safe.get(
                crypto.decrypt(self._mpw, self._one_shot),
                service,
                category
            )
            self.uname.setText(uname)
            self.pw.setText(pw)
        else:
            self.btn_show_hide.setText(self.tr(u'Show'))
            if self.selected_item is not None:
                self.uname.setText(u'***')
                self.pw.setText(u'***')
            else:
                self.uname.clear()
                self.pw.clear()
            self.statusbar.clearMessage()

    def on_btn_add(self):
        pw_1 = unicode(self.pw_new.text())
        pw_2, ok = QtGui.QInputDialog.getText(self,
            self.tr(u'Security'),
            self.tr(u'Please re-enter password:'),
            QtGui.QLineEdit.Password)
        pw_2 = unicode(pw_2)
        service = unicode(self.service.text())
        uname = unicode(self.uname_new.text())
        if not pw_1 or not service or not uname:
            QtGui.QMessageBox.information(self,
                self.tr(u'Information'),
                self.tr(u'You must provide service, username and password.'))
            return
        if ok and (pw_2 == pw_1):
            self._safe.add(
                crypto.decrypt(self._mpw, self._one_shot),
                service,
                uname,
                pw_1,
                unicode(self.combo_category.currentText())
            )
            self._safe.sync(crypto.decrypt(self._mpw, self._one_shot))
            self.service.clear()
            self.uname_new.clear()
            self.pw_new.clear()
            self.init(False)
        elif ok and (pw_2 != pw_1):
            QtGui.QMessageBox.warning(self, self.tr(u'Error'),
                self.tr(u'The passwords are not equal.'))

    def hideEvent(self, event):
        self.show_tray()
        event.accept()

    def closeEvent(self, event):
        self._safe.sync(crypto.decrypt(self._mpw, self._one_shot))
        self.write_settings()
        event.accept()


def main():
    app = QtGui.QApplication(sys.argv)
    translator = QtCore.QTranslator()
    loc = unicode(QtCore.QLocale.system().name())
    i18n_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'i18n')
    translator.load('keysafe.{0}'.format(loc), i18n_path)
    app.installTranslator(translator)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
